# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019/8/19 14:15
# @Author  : LiHaowen
# @Email   : lihaowen2017@163.com
from __future__ import unicode_literals
import uuid
from collections import Counter  # 计数类

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey,GenericRelation
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.conf import settings
from slugify import slugify
from taggit.managers import TaggableManager
from markdownx.models import MarkdownxField
from markdownx.utils import markdownify


@python_2_unicode_compatible
class Vote(models.Model):
    """使用Django中的ContentType，同时关联用户对问题和回答的投票"""
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="qa_vote", null=True, blank=True,
                             on_delete=models.SET_NULL, verbose_name="用户")
    value = models.BooleanField(default=True, verbose_name="赞同或反对")  # True赞同，False反对
    # GenericForeignKey设置
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="votes_on")
    # 其他表的主键，下面使用该通用类外键表的主键，Question表主键是自增长的int型，Answer表主键是uuid，所以这里用CharField
    object_id = models.CharField(max_length=255)  # 其它模型的主键id
    # 等同于GenericForeignKey()上边两个字段名称如果修改后则一定要在括号中
    vote = GenericForeignKey("content_type", "object_id")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        verbose_name = "投票"
        verbose_name_plural = verbose_name
        db_table = "tb_vote"
        unique_together = ("user", "content_type", "object_id")  # 联合唯一键，用户对问题或者回答赞或者踩只能点一次
        # SQL优化属性
        # 联合唯一索引，通过content_type查找文件和模型类再利用object_id找到记录所以设置联合唯一索引可以优化SQL
        index_together = ("content_type", "object_id")


@python_2_unicode_compatible
class QuestionQuerySet(models.query.QuerySet):
    """自定义QuerySet，提高模型类的可用性增加queryset api，对象.object.自定义方法"""
    def get_answered(self):
        """已有采纳答案的问题"""
        return self.filter(has_answer=True).select_related('user')

    def get_unanswered(self):
        """还没有采纳答案的问题"""
        return self.filter(has_answer=False).select_related('user')

    def get_counted_tags(self):
        """统计所有问题标签的数量(大于0的)"""
        tag_dict = {}
        # annotate(tagged=models.Count('tags')) 根据tags字段聚合分组
        # query = self.all().annotate(tagged=models.Count('tags')).filter(tags__gt=0) # 打标签后至少为1，不需要判断
        for obj in self.all():
            for tag in obj.tags.names():
                if tag not in tag_dict:
                    tag_dict[tag] = 1
                else:
                    tag_dict[tag] += 1
        return tag_dict.items()


@ python_2_unicode_compatible
class Question(models.Model):

    STATUS = (("D", "Draft"), ("O", "Open"), ("C", "Close"))
    # 草稿，开放(无已接受答案)，关闭(已有接受答案)
    # 因为用户可能因为某些原因被注销，所以user可以设置为空
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="q_author",
                             null=True, blank=True, verbose_name="提问者")
    title = models.CharField(max_length=255, unique=True, verbose_name="标题")
    slug = models.SlugField(max_length=255, verbose_name="(URL)别名")
    status = models.CharField(max_length=1, choices=STATUS, default="O", verbose_name="问题状态")
    content = MarkdownxField(verbose_name="内容")
    # tags字段当做多对多标签使用，将taggit添加至应用
    tags = TaggableManager(help_text="多个标签使用,(英文)隔开", verbose_name="标签")
    has_answer = models.BooleanField(default=False, verbose_name="接受回答")  # 是否有接受的回答
    votes = GenericRelation(Vote, verbose_name="投票情况")  # 通过GenericRelation关联到vote表，不是实际的字段
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name="创建时间")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    objects = QuestionQuerySet.as_manager()

    class Meta:
        verbose_name = "问题"
        verbose_name_plural = verbose_name
        db_table = "tb_question"
        ordering = ("-created_at", )

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        """重写save方法将标签名利用slugify进行别名存储到slug中"""
        if not self.slug:
            self.slug = slugify(self.title)
        super(Question, self).save()

    def __str__(self):
        return self.title

    def get_markdown(self):
        return markdownify(self.content)

    def total_votes(self):
        """得票数(点赞数-踩数)"""
        # self.votes.values_list = Vote.objects.values_list
        # self.votes.values_list("value", flat=True) 返回只包含true 和false的列表
        # Counter计数类，将其变为一个字典{"True": 数量,"False": 数量}
        dic = Counter(self.votes.values_list("value", flat=True))  # Counter 赞同票多少，反对票多少
        return dic[True] - dic[False]

    def get_answers(self):
        """获取所有的回答"""
        return Answer.objects.filter(question=self).select_related('user', 'question')  # self作为参数，当前问题的回答

    def count_answer(self):
        """回答的数量"""
        return self.get_answers().count()  # self作为参数，回答的数量

    def get_upvoters(self):
        """赞同的用户"""
        return [vote.user for vote in self.votes.filter(value=True).select_related('user').prefetch_related('vote')]

    def get_downvoters(self):
        """反对的用户"""
        # self.votes.filter(value=False) = Vote.object.filter(value=False)
        return [vote.user for vote in self.votes.filter(value=False).select_related('user').prefetch_related('vote')]

    def get_accepted_answer(self):
        """被接受的回答"""
        return Answer.objects.filter(question=self, is_answer=True).first()


@python_2_unicode_compatible
class Answer(models.Model):
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="a_author", on_delete=models.SET_NULL,
                             null=True, blank=True, verbose_name="回答者")
    question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name="问题")
    content = MarkdownxField(verbose_name="内容")
    is_answer = models.BooleanField(default=False, verbose_name="回答是否被接受")
    votes = GenericRelation(Vote, verbose_name="投票情况")  # 通过GenericRelation关联到vote表，不是实际的字段
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name="创建时间")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        verbose_name = "回答"
        verbose_name_plural = verbose_name
        db_table = "tb_answer"
        ordering = ("-is_answer", "-created_at")  # 多字段排序，被采纳的回答优先，其余按照创建时间进行排序

    def __str__(self):
        return self.content[:15]

    def get_markdown(self):
        return markdownify(self.content)

    def total_votes(self):
        """得票数(点赞数-踩数)"""
        # self.votes.values_list = Vote.objects.values_list
        # self.votes.values_list("value", flat=True) 返回只包含true 和false的列表
        # Counter计数类，将其变为一个字典{"True": 数量,"False": 数量}
        dic = Counter(self.votes.values_list("value", flat=True))  # Counter 赞同票多少，反对票多少
        return dic[True] - dic[False]

    def get_upvoters(self):
        """赞同的用户"""
        return [vote.user for vote in self.votes.filter(value=True).select_related('user').prefetch_related('vote')]

    def get_downvoters(self):
        """反对的用户"""
        # self.votes.filter(value=False) = Vote.object.filter(value=False)
        return [vote.user for vote in self.votes.filter(value=False).select_related('user').prefetch_related('vote')]

    def accept_answer(self):
        """
        接受回答
        需要将has_answer和is_answer字段进行修改所以数据数据库处理
        """
        # 当一个问题有多个回答的时候，只能采纳一个回答，其它回答一律置为未接受
        answer_set = Answer.objects.filter(question=self.question).select_related('user', 'question')  # 查询当前问题的所有回答
        answer_set.update(is_answer=False)  # 一律置为未接受
        # 接受当前回答并保存
        self.is_answer = True
        self.save()
        # 该问题已有被接受的回答，修改字段进行保存
        self.question.has_answer = True
        self.question.save()

    # 1. 需要返回查询集的逻辑写在QuerySetModel中  利用条件筛选过后对象集合
    # 2. 模型类中的数据库处理的逻辑写在models中   具有实体含义且不存储在数据库的字段
    # 3. 业务相关逻辑的处理写在View中

